Plano de aulas

O curso ocorrerá entre 13 e 15 de dezembro e está estruturado assim:

  • Dia 1, parte 1 - Ler dados no R
  • Dia 1, parte 2 - Manupulando data frames
  • Dia 2, parte 1 - Manipulando dados com dplyr
  • Dia 2, parte 2 - Regressões
  • Dia 3, parte 1 - Visualização de dados com ggplot2
  • Dia 3, parte 2 - Exercícios propostos pela turma

Para fazer download dos dados utilizados na aula acesse esta pasta do dropbox.

Todas as aulas ainda podem ser alteradas. Exercícios seão realizados em sala para fixar as filosofias dos pacotes e seus comandos.

Aquecimento

Que tal já utilizar um pouco dos seus conhecimentos? Tente realizar o exercício abaixo. Caso não consiga realizar as duas primeiras tarefas, revisite o curso introdutório. A terceira é um desafio.

eyJsYW5ndWFnZSI6InIiLCJzYW1wbGUiOiIjIEpcdTAwZTEgc2FiZSBjcmlhciB1bWEgdmFyaVx1MDBlMXZlbD8gQXRyaWJ1aWEgMjAgXHUwMGUwIHhcblxuXG4jIFZlcmlmaXF1ZSBhIGVzdHJ1dHVyYSBkYSB0YWJlbGEgaXJpcyBjb20gc3RyKClcblxuXG4jIENhbGN1bGUgYSBtXHUwMGU5ZGlhcyBkbyBjdW1wcmltZW50byBkYXMgcFx1MDBlOXRhbGFzIHBvciBlc3BcdTAwZTljaWUiLCJzb2x1dGlvbiI6IiMgSlx1MDBlMSBzYWJlIGNyaWFyIHVtYSB2YXJpXHUwMGUxdmVsPyBBdHJpYnVpYSAyMCBcdTAwZTAgeFxueCA8LSAyMFxuXG4jIFZlcmlmaXF1ZSBhIGVzdHJ1dHVyYSBkYSB0YWJlbGEgaXJpcyBjb20gc3RyKClcbnN0cihpcmlzKVxuXG4jIENhbGN1bGUgYSBtXHUwMGU5ZGlhcyBkbyBjdW1wcmltZW50byBkYXMgcFx1MDBlOXRhbGFzIHBvciBlc3BcdTAwZTljaWVcbmFnZ3JlZ2F0ZShQZXRhbC5MZW5ndGggfiBTcGVjaWVzLCBkYXRhID0gaXJpcywgbWVhbikiLCJzY3QiOiJ0ZXN0X29iamVjdChcInhcIilcbnRlc3Rfc3R1ZGVudF90eXBlZChjKFwidGFwcGx5KGlyaXMkUGV0YWwuTGVuZ3RoLCBpcmlzJFNwZWNpZXMsIG1lYW4pXCIsXG4gICAgICAgICAgICAgICAgICAgICBcImFnZ3JlZ2F0ZShQZXRhbC5MZW5ndGggfiBTcGVjaWVzLCBkYXRhID0gaXJpcywgbWVhbilcIiksXG4gICAgICAgICAgICAgICAgICAgbm90X3R5cGVkX21zZyA9IFwiTmFvIGNvbnNlZ3VpdSBhY2hhciB1bWEgc29sdWNhbz8gRmlxdWUgdHJhbnF1aWxvLCB2b2NcdTAwZWEgc2FiZXJhIGFvIGZpbmFsIGRvIGN1cnNvLiBBdGUgbGEgdm9jXHUwMGVhIHBvZGUgY2xpY2FyIGVtIGBzb2x1dGlvbmAgZSB2ZXIgYSBzb2x1Y2FvIHByb3Bvc3RhIChuYW8gZWggYSB1bmljYSkuXCIpXG5zdWNjZXNzX21zZyhcIk1hbmRvdSBiZW0hIVwiKSJ9
Ler dados no R

O R vem como algumas funções nativas para ler dados. Entre elas as mais comuns são:

Função tipo de arquivo
read.table() “.txt”, “.csv”, “.tsv”
read.csv() “.csv” (sep = “,”)
read.csv2() “.csv” (sep = “;”)
read.fwf() variáveis com tamanhos fixos
muito usado em microdados
readLines() lê linhas como são

Vamos testar estas funções?

Exemplos do R básico

csv <- read.csv2("teste.csv")
 str(csv) # Textos se tornaram fatores
## 'data.frame':    10000 obs. of  2 variables:
 ##  $ texto: Factor w/ 25 levels "a","b","c","d",..: 1 2 3 4 5 6 7 8 9 10 ...
 ##  $ num  : int  1 2 3 4 5 6 7 8 9 10 ...
csv2 <- read.csv2("teste.csv",
                   stringsAsFactors = FALSE)
 str(csv2)
## 'data.frame':    10000 obs. of  2 variables:
 ##  $ texto: chr  "a" "b" "c" "d" ...
 ##  $ num  : int  1 2 3 4 5 6 7 8 9 10 ...

Formatos do R

Função tipo de arquivo Quando usar?
saveRDS() “.RDS” salvar um objeto
readRDS() “.RDS” ler um objeto
save() “.RData” ou “.RDA” salvar vários objetos
load() “.RData” ou “.RDA” ler vários objetos

Formatos do R

RDS <- readRDS("teste.RDS")
 str(RDS)
## 'data.frame':    10000 obs. of  2 variables:
 ##  $ texto: chr  "a" "b" "c" "d" ...
 ##  $ num  : int  1 2 3 4 5 6 7 8 9 10 ...
load("teste.RDA") # Não precisa atribuir
 str(RDA)
## 'data.frame':    10000 obs. of  2 variables:
 ##  $ texto: chr  "a" "b" "c" "d" ...
 ##  $ num  : int  1 2 3 4 5 6 7 8 9 10 ...

Para que tantos formatos??

Para que eu preciso de tantas formas de ler e escrever dados??

Bem, você geralmente não controla o formato do arquivo que conterá os dados que você usará. Por isso é importante conhecer variadas formas ler os mais variados formatos.

Lendo arquivos do Excel

O R “puro” - isto é, assim que você abre ele - não lê arquivos com as extensões clássicas do excel “.xls” e “.xlsx”. Contudo, tem muitos pacotes que permitem ler e escrever arquivos com estes formatos.

Pacote Bônus Ônus
readxl muito rápido não exporta arquivos
xlsx exporta arquivos trava frequentemente
(acesso por Java)
openxlsx rápido, le e
exporta arquivos mais lento que readxl

Mesmo assim, não recomendo usar “.xlsx” por dois motivos: i) Há um limite de 1 mihão de linhas e ii) até com os bons pacotes, demora a gerar o arquivo.

Exportando dados

Além das funções de leitura read.*, Há também funções para escrever arquivos: write.*. Os sufixos se repetem nas duas famílias de funções.

Função tipo de arquivo
write.table() “.txt”, “.csv”, “.tsv”
write.csv() “.csv” (sep = “,”)
write.csv2() “.csv” (sep = “;”)
writeLines() escreve linhas como são
openxlsx::write.xlsx() ‘.xlsx’

Comparando “.xslx” e “.csv”

Até as melhores opções para escrever um “.xlsx” são mais lentas que o R básico.

library(microbenchmark)
 library(openxlsx)
 
 microbenchmark(times = 30,
   xlsx = write.xlsx(RDA, "teste.xlsx"),
   csv = write.csv2(RDA, "teste2.csv"))
## Unit: milliseconds
 ##  expr   min    lq  mean median    uq    max neval
 ##  xlsx 308.4 400.7 502.3  458.3 524.6 1376.4    30
 ##   csv  22.6  25.3  36.2   32.4  41.2   74.4    30

Comparando a performance

microbenchmark(times = 30, unit = "ms",
   csv = read.csv2("teste.csv"),
   RDA = load("teste.RDA"),
   RDS = readRDS("teste.RDS"),
   readxl = readxl::read_excel("teste.xlsx"),
   openxlsx = read.xlsx("teste.xlsx"))
## Unit: milliseconds
 ##      expr    min     lq   mean median     uq   max neval
 ##       csv   8.71   9.72  11.68  11.26  12.27  16.5    30
 ##       RDA   3.48   3.66   4.52   4.01   4.73  10.1    30
 ##       RDS   3.18   3.47   5.33   3.98   7.04  16.4    30
 ##    readxl  33.70  35.89  59.87  38.92  44.33 592.4    30
 ##  openxlsx 109.78 125.50 159.22 148.69 185.00 274.1    30

Tamanho dos formatos

Salvamos os mesmo dados em variados formatos. Vejamos a diferença de tamanho entre eles:

file.size("teste.csv") / 2^10 # em Kb
## [1] 96.6
file.size("teste.RDS") / 2^10 # em Kb. 6 vezes menor!
## [1] 21.1
file.size("teste.RDA") / 2^10 # em Kb. Igual!
## [1] 21.1
file.size("teste.xlsx") / 2^10 # em Kb
## [1] 124

Dados de outros pacotes estatísticos

O pacote haven possui funções para ler formatos de outros softwares como STATA, SPSS e SAS

software função
SAS write_sas
SAS read_sas
Stata write_dta
Stata read_dta
SPSS write_sav
SPSS read_sav

Dados de bancos de dados

Há inumeros pacotes para ler dados armazenados em bases de dados. Alguns são:

pacote Base
RMySQL MySQL
RDOBC SQL Server
RPostgres Postgres
RSQLite SQLite
rmongodb MongoDB
RCassandra Cassandra
Manipulando dados

Manipulando dados com o R básico

O curso online realizado já cobriu um pouco da sintaxe de manipulação de dados. Vamos das principais funções.

Toda análise de dados começa importando os dados:

load('Microdados/Censo_Edu_Superior_2014.RDA')
 ls() # quais objetos foram carregados
## [1] "docente2014" "grad2014"    "ies2014"     "local2014"

Noções gerais dos dados importados

  • Funções essenciais para conhecer os dados
Função ação
str() estrutura dos dados
summary() sumário dos dados, de acordo com a classe
class() classe do objeto
length() tamanho do objeto
names() nomes do objeto

A estrutura dos dados

Tente usar a função str() nas tabelas importadas:

str(docente2014)
 
 str(grad2014)
 
 str(ies2014)
 
 str(local2014)

Sumário dos dados

Agora vamos conhecer um pouco de algumas variáveis. Tente os comandos abaixo e observe as os resultados no console.

summary(factor(docente2014$DS_CATEGORIA_ADMINISTRATIVA))
 # conta as categorias
 summary(grad2014$QT_VAGAS_NOVAS_EAD)
 # sumário de 6 números
 summary(local2014$DS_LOGRADOURO)
 # informa ser texto
 summary(ies2014)
 # sumário de todas variáveis

Classes, tamanho e nomes

class(docente2014) # data.frame
 class(grad2014$QT_CONCLUINTE_CURSO) # inteiro
 
 length(local2014) # conta variáeis
 length(local2014$CO_IES) # quantidade de observações
 
 names(ies2014) # nomes das variáveis
 names(docente2014) # nomes das variáveis

primeiras ou últimas observações

Há também a opçao de você ver apenas algumas observações com as funções head(objeto, quantidade) e tail(objeto, quantidade)

head(grad2014$QT_CONCLUINTE_CURSO)
 tail(grad2014$QT_CONCLUINTE_CURSO)
 # 6 por padrão
 
 head(ies2014$VL_RECEITA_PROPRIA, 10)
 # primeiros 10
 tail(docente2014$DS_ESCOLARIDADE_DOCENTE, 3)
 # 3 últimos

Selecionando partes dos dados

Como observamos até agora todas as tabelas são grandes. Para este exercício só precisamos de algumas variáveis. Mas como fazer isso? O R básico nós da algumas opções:

  • Funções essenciais para selecionar parte dos dados:
  1. [
  2. [[
  3. $
  4. subset()
  5. with()

Seleção com [ , ]

A primeira forma de realizar uma seleção é colocar colchetes até o objeto e, dentro dos colchetes, inserir o nome ou indice do valor desejado. No caso de data frames podemos indicar as linhas e colunas que desejamos.

data.frame[linhas, colunas]

docente2014[1:1000 , c("NO_IES", 
                        "DS_SEXO_DOCENTE",
                        "IN_SUBSTITUTO")]
 # Apenas as primeiras 1000 observações
 # das 3 variáveis selecionadas
 
 docente2014[1:10 , ] # 10 observações
 docente2014[ ,c("NO_IES", 
                 "DS_SEXO_DOCENTE",
                 "IN_SUBSTITUTO")]
 # todas observações das 3 variáveis

Seleção com [[ ]]

A seleção com os colchetes duplos escolhe apenas um elemento do objeto. Também são aceitos tanto o índice quanto o nome.

objeto[[elemento]]

Observe que enquanto [] retorna um data.frame, [[]] retorna apenas o vetor.

ultima <- grad2014[[length(grad2014)]]
 # usando número
 laboratorio <- grad2014[["IN_POSSUI_LABORATORIO"]]
 # usando nome
 laboratorio[[500]]
## [1] 1
# apenas a 500ª observação

Seleção com $

O $ funciona de modo semelhante ao [[]], isto é, retorna apenas um elemento. Mas há uma diferença, o $ só aceita nomes para realizar as seleções.

lab <- grad2014$IN_POSSUI_LABORATORIO
 all.equal(lab, laboratorio) # é igual
## [1] TRUE
docente2014$1 # erro
## Error: <text>:1:13: unexpected numeric constant
 ## 1: docente2014$1
 ##                 ^

Função subset()

A sintaxe da função é como segue:

subset(x, subset, select, drop = FALSE, ...)

subst <- subset(docente2014,
                 subset = IN_SUBSTITUTO == 1,
                 select = c("DS_SEXO_DOCENTE", "NU_IDADE_DOCENTE"))
 
 subst2 <- subset(docente2014,
                 subset = IN_SUBSTITUTO == 1,
                 select = c(17, 21))
 all.equal(subst, subst2)
## [1] TRUE

Função with()

A funçao with() tira a necessidade de ficar digitando a cada vez o nome do objeto principal. Assim,

plot(ies2014$VL_RECEITA_PROPRIA,
      (ies2014$QT_TEC_DOUTORADO_MASC + ies2014$QT_TEC_DOUTORADO_FEM))

pode ser reescrita como

with(ies2014,
      plot(x = QT_TEC_DOUTORADO_MASC + QT_TEC_DOUTORADO_FEM,
           y = VL_RECEITA_PROPRIA))

Sintetizando os dados

  • Funções essenciais para sumarizar data.frames:
Função ação
lapply() executa uma função em cada elemento
sapply() idem, resultado simplificado
tapply() iden, dado um critério
aggregate() agrega por algum critério
transform() cria variável calculada

lapply e sapply

As funções lapply() e sapply() executam uma mesma função para para cada elemento de um objeto. No caso dos data frmaes, a função será aplicada em cada variável.

A diferença entre as duas está na objeto que retornam: enquanto lapply retorna uma sempre lista, sapply tenta simplificar o resultado para um vetor, matriz ou mesmo um data frame.

lapply e sapply

classe <- sapply(ies2014, class)
 # classe de todas viariáveis
 sapply(ies2014[, classe == "numeric"],
        mean) /1000000 # em milhões
##         VL_RECEITA_PROPRIA           VL_TRANSFERENCIA 
 ##                 102.734132                  33.833887 
 ##           VL_OUTRA_RECEITA VL_DES_PESSOAL_REM_DOCENTE 
 ##                  23.320635                  37.546380 
 ## VL_DES_PESSOAL_REM_TECNICO     VL_DES_PESSOAL_ENCARGO 
 ##                  18.606625                  22.393972 
 ##             VL_DES_CUSTEIO        VL_DES_INVESTIMENTO 
 ##                  41.580863                   9.319011 
 ##            VL_DES_PESQUISA              VL_DES_OUTRAS 
 ##                   1.253679                  15.526447
# média só dos vetores numéricos

tapply

A função tapply() é da mesma família, mas ela leva um argumento a mais, um índice. É como se pedíssemos “Execute FUN no vetor X para cada categoria de INDEX”

tapply(ies2014$VL_RECEITA_PROPRIA,
        INDEX = ies2014$DS_CATEGORIA_ADMINISTRATIVA,
        FUN = mean)/1000000 # em milhões
##                    Especial Privada com fins lucrativos 
 ##                    49.56429                   140.34667 
 ## Privada sem fins lucrativos            Pública Estadual 
 ##                    87.62236                    23.75046 
 ##             Pública Federal           Pública Municipal 
 ##                    44.02261                    11.72985

tapply com sapply

Para ter a média por categoria de instituição para todas as variáveis numéricas bosta aplicar o tapply() em cada uma das variáveis numéricas:

sapply(ies2014[, classe == "numeric"], FUN = tapply,
        INDEX = ies2014$DS_CATEGORIA_ADMINISTRATIVA,
        mean)
 
 # Cuidado com o nome dos argumentos
 # tanto tapply quanto apply tem FUN como argumento

dplyr: uma gramática para manipulação de dados

Antes de mais nada é necessário baixa e carregar o pacote.

install.packages('dplyr') # nome como texto
 library(dplyr) 

Os verbos do dplyr

O dplyr é pensado na forma de uma gramática em que as principais tarefas de manipulação de data frames foi transformada em funções. São elas:

  1. filter() - filtre
  2. summarise() - sumarize
  3. select() - selecione
  4. mutate() - crie uma variável
  5. arrange() - organize
  6. groupe_by() - agrupe

O pipe operator (isto não é um operador)

Isto não é um cachimbo.

Isto não é um cachimbo.

O pipe operator (isto não é um operador)

Outra ponto chave na facilidade da sintaxe do dplyr é o %>% (pipe operator). Ele torna a construção do código mais parecida a nossa linguagem ao tornar o objeto que o precede como primeiro argumento da função que o segue. Ou seja, ele não faz nada, não é um operador! Vejamos:

Mostre uma tabela com a escolaridade dos 30 últimos professores substitutos da UFSC:

table(
   tail(docente2014[
     docente2014$NO_IES == "UNIVERSIDADE FEDERAL DE SANTA CATARINA" &
       docente2014$IN_SUBSTITUTO == 1,
     "DS_ESCOLARIDADE_DOCENTE"], 30))

O mesmo com dplyr

Mostre uma tabela com a escolaridade dos 30 últimos professores substitutos da UFSC:

docente2014 %>%
   filter(NO_IES == "UNIVERSIDADE FEDERAL DE SANTA CATARINA",
          IN_SUBSTITUTO == 1) %>%
   select(DS_ESCOLARIDADE_DOCENTE) %>% tail(30) %>% table()

Mas antes…

Antes de seguirmos vamos limpar nossa área de trabalho e carregar os dados de domicílios da PNAD.

rm(list = ls())
 
 load('Microdados/domicilios_2014.RDA')
 
 dim(domicilios_2014) # mais de 150 mil observações!
## [1] 151291     83

Observação: A PNAD apresenta dados de amostra e as generalizações devem ser cuidadosas.

select() - selecione

O verbo select() permite selecionar apenas uma variável (coluna) da tabela. Isto é muito necessário quando trabalhamos microdados porque raramente você precisará das 83 variáveis que compõe a tabela domicílios.

Depois de verificar o dicionário de variáveis da pesquisa, decidirmos fica com as seguintes variáveis:

NOME conteúdo NOME conteúdo
V0102 “numero de controle” V0104 “tipo de entrevista”,
V0105 “total de moradores” V0201 “espécie de domicílio”,
V0205 “número de cômodos” V0208 “aluguel”,
V0209 “prestação” V0230 “tem maquina”,
V02321 “tem tablet” V4621 “renda mensal per capta”

select() - selecione

Os argumentos do select são: i) os dados e ii) as colunas selecionadas. As colunas não precisam ser escritas entre aspas. É possível renomear as colunas ao selecionar.

domicilios <- domicilios_2014 %>%
   select(controle = V0102, tipo = V0104, moradores = V0105,
          domicílio = V0201, cômodos = V0205,
          aluguel = V0208, prestação = V0209,
          maquina = V0230, tablet = V02321, renda = V4621)
 
 length(domicilios) # só as 10 variáveis que escolhemos
## [1] 10

select() - um pouco mais

Além da facilidade visual e de sintaxe, o select() apresenta algumas outras forma interessantes de selecionar as variáveis desejadas. Digite ?select e dê uma olhada nos exemplo oferecidos.

domicilios %>% select(domicílio:prestação)
 
 domicilios %>% select(contains('a'))
 
 domicilios %>% select(starts_with("m"))

mutate() - crie uma variável

Outra função relaciona à variáveis é a mutate. Ela permite criar novas variáveis para cada observação utilizando outras variáveis da tabela. Ela é similar a função transform() do R-base, mas a mutate pode usar na mesma chamada variáveis que ela está criando.

O Código da UF dos domicíos está “escondido” nos primeiros dois números das variável de controle. Vamos criar a variável UF

domicilios <- domicilios %>% 
   mutate(UF = substr(controle, 1, 2), SC = UF == 42)
 
 str(domicilios)
## Classes 'tbl_df', 'tbl' and 'data.frame':    151291 obs. of  12 variables:
 ##  $ controle : chr  "11000015" "11000015" "11000015" "11000015" ...
 ##  $ tipo     : chr  "01" "01" "01" "01" ...
 ##  $ moradores: chr  "03" "02" "01" "05" ...
 ##  $ domicílio: chr  "1" "1" "1" "1" ...
 ##  $ cômodos  : int  3 6 3 12 5 5 7 5 NA 6 ...
 ##  $ aluguel  : num  376 NA NA NA NA NA NA NA NA NA ...
 ##  $ prestação: num  NA NA NA NA NA NA NA NA NA NA ...
 ##  $ maquina  : chr  "4" "2" "2" "2" ...
 ##  $ tablet   : chr  "4" "4" "4" "2" ...
 ##  $ renda    : num  500 1150 724 1700 855 ...
 ##  $ UF       : chr  "11" "11" "11" "11" ...
 ##  $ SC       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...

filter() - filtre

A função filter() recebe dois argumentos: i) a tabela que será filtrada e ii) os critérios de filtragem. Estes critérios devem ser de tipo lógico e podem ser quantos forem necessários.

vamos filtrar apenas os dados dos domicílios de Santa Catarina

dom_SC <- domicilios %>% filter(UF == 42)
 dom_SC2 <- domicilios %>% filter(SC)
 
 identical(dom_SC, dom_SC2)
## [1] TRUE
nrow(dom_SC)
## [1] 4484

filter() - filtre

Se houver mais de um predicado, eles serão unidos por &. Vamos pegar apenas as observações dos domicílios de SC que tinham tablet.

tablet_SC <- domicilios %>%
   filter(UF == 42, 
          tablet == 2) # 2 é sim, dicionário da pesquisa
 
 (nrow(tablet_SC) / nrow(dom_SC)) %>% round(2) # %
## [1] 0.12

summarise() - resuma

A função summarise() cria um resumo (sumário) dos dados. Mas qual resumo? O que você solicitar. A característica principal desta função é que ela ela retornará apenas um resultado. Por exemplo: qual a menor renda domicíliar de Santa Catarina? E a maior? Qual o número de pessoas que não tem máquina de lavar? Assim por diante

Seus argumentos são: i) a tabela e ii) Como resumir os dados.

dom_SC %>%
   summarise(moradores = sum(as.numeric(moradores),
                             na.rm = TRUE), # nomes iguais, sem problemas
             minima = min(renda, na.rm = TRUE),
             maxima = max(renda, na.rm = TRUE),
             maquina = sum(maquina == 4, na.rm = TRUE))
## # A tibble: 1 × 4
 ##   moradores minima       maxima maquina
 ##       <dbl>  <dbl>        <dbl>   <int>
 ## 1      9701      0 999999999999     520

arrange() - organize

arrange() organiza os dados baseado em uma das variáveis. Por padrão arrange() usa a ordem crescente, mas se você quiser fazê-lo pela ordem decrescente basta usar o adjetivo desc().

dom_SC %>% select(cômodos, renda) %>%
   arrange(renda) %>% head()
## # A tibble: 6 × 2
 ##   cômodos renda
 ##     <int> <dbl>
 ## 1       8     0
 ## 2       5     0
 ## 3       5     0
 ## 4       6     0
 ## 5      10     0
 ## 6       4     0
# Ou os de maior renda
 dom_SC %>% select(cômodos, renda) %>%
   arrange(desc(renda)) %>% head()
## # A tibble: 6 × 2
 ##   cômodos        renda
 ##     <int>        <dbl>
 ## 1       9 999999999999
 ## 2       3 999999999999
 ## 3       8 999999999999
 ## 4       7 999999999999
 ## 5       3 999999999999
 ## 6       7 999999999999
# Parece ter algo errado com o renda. Leia a documentação

Separar Aplicar e Combinar (SAC)

Até agora nossa análise tem sido muito genérica considerando toda a população de Santa Catarina como uma coisa única. Seria bom poder separá-la em grupos ou classes, não? A estratégia de Separar os dados, Aplicar um regra e Combinar eles visa permitir esse tipo de análise sem perder o todo de vista. E o dplyr oferece um verbo fundamental para usar esta estratégia: group_by()

groupe_by() - agrupe

A função group_by(), diferentemente das outas, não tem utilidade sozinha. Ela cria grupos de dados que compreendam todas as combinação dos critérios que passarmos. Se você quiser remover os grupos da tabela (apenas um atributo), use ungroup().

Eliminaremos as rendas de 999999999999, pois represetam não reclaração.

dom_SC %>% filter(renda != 999999999999) %>%
   group_by(maquina, tablet) %>%
   summarise(minima = min(renda),
             media = mean(renda),
             maxima = max(renda),
             quantidade = n())
## Source: local data frame [5 x 6]
 ## Groups: maquina [?]
 ## 
 ##   maquina tablet minima     media maxima quantidade
 ##     <chr>  <chr>  <dbl>     <dbl>  <dbl>      <int>
 ## 1       2      2      0 2247.8511  20000        470
 ## 2       2      4      0 1449.8897  23000       2320
 ## 3       4      2    316 1286.2800   3000         25
 ## 4       4      4      0  960.6358   7700        486
 ## 5    <NA>   <NA>    473 1227.8333   1870          6

Fazendo regressões no R

O R possui funções próprias para realizar regressões. A principal delas é a função lm() e sua sintaxe é bastante direta e apenas um argumento é necessário: a fórmula.

lm(var_explicada ~ log(var_que_explica1) + (var_que_explica2 ^ var_que_explica3))

Fórmula

A fórmula, por sua vez pode ser escrita de várias maneiras.

Sintaxe modelo
Y ~ A Y = ß0 + ß1A
Y ~ -1 + A Y = ß1A
Y ~ A + I(A^2) Y = ß0+ ß1A + ß2A2
Y ~ A + B Y = ß0+ ß1A + ß2B
Y ~ A:B Y = ß0 + ß1AB
Y ~ A*B Y = ß0 + ß1A + ß2B + ß3AB
Y ~ (A + B + C)^2 Y = ß0+ ß1A + ß2B + ß3C + ß4AB + ß5AC + ß6AC

Fazendo seu modelo

Para demonstrar o uso da função lm() vamos criar uma variável aleatória var1 e o erro do modelo e vamos construir um y baseado neles. Após, pediremos ao R que defina a relação entre a variável var1 e y e guarde em modelo.

set.seed(1)
 var1 <- rnorm(50) # apenas 50 observações
 erro <- rnorm(50)
 
 y = 2.3 * var1 + erro
 modelo <- lm(y ~ log(var1))
## Warning in log(var1): NaNs produzidos

Conhecendo o modelo

Mas o que é um modelo? Uma fórmula? E os parâmetros? Ele também tem erros, medidas de precisão como o R², etc. Qual delas estará guardada em modelo? Vamos descobrir!

print(modelo)
 summary(modelo) 
 str(modelo)

Visualizando o modelo

A visualização do modelo

plot(log(var1), y)
## Warning in log(var1): NaNs produzidos
abline(modelo, col = 'red', lwd = 2) 

Visualizando propriedades do modelo

Outro passo importante na análise de modelos é verificar algumas de suas propriedades e os pressupostos são respeitados.

layout(matrix(1:4, 2, 2))
 plot(modelo) 

Modelo Ensino Superior

Agora é sua hora de criar modelos para o lucro das instituições de ensino superior privada em função do número de profissionais em determinado nível de ensino. Crie um modelo para cada um dos níveis de ensino.

Funções

Estamos usando funções no R desde que começamos. Mas o que são funções? As funções são comandos que recebem parâmetros de entrada, fazem alguma coisa e retornam algum resultado.

saida <- função(entrada)

A função pode ser divida em três partes: nome, argumentos e corpo. Um das grande vantagens do R enquanto linguagem de programação é que podemos criar nossas prórpias funções.

nome <- function(argumento1, argumento2, ...) {
   # corpo da função
   
   # aqui algo é feito com argumento1 e argumento2
   
 }

Sua primeira função

Vamos criar uma função que recebe um vetor, conta os NAs dele e retorna este número.

conta_na <- function(x) {
   sum(is.na(x))
 }
 
 conta_na(c(NA, 3, NA, 2, 4, 5, NA))
## [1] 3
conta_na(1:6)
## [1] 0

Por que escrever uma função?

Evita copiar e colar código, o que diminui chance de erro. Além disso, quando você descobrir que há um erro em seu código (e você vai!), só uma alteração será necessária, aquela que define a função.

Você ainda pode usar um função em outra situação. Digamos que você tenha feito esta função conta_na() para fazer uma análise da PNAD. Quando você for analisar os dados da RAIS, não vai precisar escrever outra vez o mesmo código.

Quando escrever a função?

Há uma idéia de que você deve escrever uma função quando executa uma tarefa pela terceira vez. Se você está precisando repetir alguma rotina e está em dúvida se deve escrever um função, provavelmente é porque deve escrevê-la.

Argumentos padrão

As funções podem ter quantos argumentos você achar necessário. Mas você pode não querer usar todos argumentos em cada vez que rodar a função. Para isso existem os argumentos padrão: se há um padrão para determinado argumento e você não o expecifica, o padrão é utilizado. Lembra das funções read.table() e read.csv2()? A diferença entre elas está nos argumentos padrão.

elevado <- function(x, n = 2) {
   x ^ n
 }
 
 elevado(2, 4)
## [1] 16
elevado(2) # ambas funcionam
## [1] 4

Argumento especial: “…”

Ainda vou escrever

Recurssão

Uma função é recursiva quando ela chama a si própria até que ela não chame mais. Funções recursivas são muito mais eficientes Exemplo: função que conta de um número até zero.

ate_0 <- function(x) {
   if (x >= 0) {
     print(x)
     ate_0(x-1)
   } else return()
   
 }

Não se esqueça de especificar um caso base (onde decide parar de se chamar). Caso contrário, ocorrerá um loop infinito (que pode ser cancelado com a tacla ESQ .

Operadores binários

Já parou para pensar como o + ou o \ funcionam no R? Sim! Eles também são funções. Isso quer dizer que você pode criar seus próprios operadores se quiser.

`%Op%` <- function(x, funcao) funcao(x)
 
 1:10 %Op% mean
## [1] 5.5

Escopo

Ainda vai ter texto

Métodos

Já observou dependendo do objeto o R decide imprimi-lo diferente, ou mostrar o sumário diferente? O R faz isso baseado na classe do objeto que passamos a ele. Há, em alguns casos, distintos métodos para classes diferentes. Veja methods(print).

O que acontece se usamos a conta_na() em um data frame? Ele retorna o número todos de NA em todo o data frame. Mas isso não é tão útil. Queremos que, quando o argumento da função for um data frame, ele diga quantos NAs cada variável tem. Como podemos fazer isso?

Criando um método

conta_na <- function(x) {
   UseMethod("conta_na", x)
 } # sobreescrevemos a função
 
 conta_na.data.frame <- function(df) {
   sapply(df, conta_na)
 }
 
 conta_na.default <- function(x) {
   sum(is.na(x))
 } # temos que redefinir

Nota

Este texto foi originalmente publicado no blog Análise Real.

Loops: for()

Um loop utilizando for() no R tem a seguinte estrutura básica:

for(i in conjunto_de_valores){
   # comandos que 
   # serão repetidos
 }
  • O início do loop se dá com o comando for seguido de parênteses e chaves;
  • Dentro do parênteses temos um indicador que será usado durante o loop (no caso escolhemos o nome i) e um conjunto de valores que será iterado (conjunto_de_valores).
  • Dentro das chaves temos o bloco de código que será executado durante o loop.

Em outras palavras, no comando acima estamos dizendo que para cada elemento i contido no conjunto_de_valores iremos executar os comandos que estão dentro das chaves.

Para facilitar o entendimento, vejamos dois exemplos muito simples. Primeiro, vamos imprimir na tela os números de 1 a 5.

for(i in 1:5){
   print(i)
 }
 ## [1] 1
 ## [1] 2
 ## [1] 3
 ## [1] 4
 ## [1] 5

Agora, vamos imprimir na tela as 5 primeiras letras do alfabeto (o R já vem com um vetor com as letras do alfabeto: letters).

for(i in 1:5){
   print(letters[i])
 }
 ## [1] "a"
 ## [1] "b"
 ## [1] "c"
 ## [1] "d"
 ## [1] "e"

No mesmo exemplo, acima, ao invés correr o loop no índice de inteiros 1:5, vamos iterar diretamente sobre os primeiros 5 elementos do vetor letters:


 for(letra in letters[1:5]){
   print(letra)
 }
 ## [1] "a"
 ## [1] "b"
 ## [1] "c"
 ## [1] "d"
 ## [1] "e"

seq_along

Uma função bastante útil ao fazer loops é a função seg_along(). Ela cria um vetor de inteiros com índices para acompanhar o objeto.

# criando um vetor de exemplo
 set.seed(119)
 x <- rnorm(10)
 
 # inteiros de 1 a 10
 seq_along(x)
 ##  [1]  1  2  3  4  5  6  7  8  9 10

Também é possível criar um vetor de inteiros do tamanho do objeto fazendo uma sequência de 1 até length(x):

1:length(x)
 ##  [1]  1  2  3  4  5  6  7  8  9 10

Entretanto, a vantagem de seq_along() é que quando o vetor é vazio, ela retorna um vetor vazio e, deste modo, o loop não é executado (o que é o comportamento correto).

Já a sequência 1:length(x) retorna a sequência 1:0, isto é, uma sequência decrescente de 1 até 0, e loop é executado nestes valores.

Vejamos:

# cria vetor vazio
 x <- numeric(0)
 
 # 1:length(x)
 # note que o loop é executado (o que é errado)
 for(i in 1:length(x)) print(i)
 ## [1] 1
 ## [1] 0
 
 # seq_along
 # note que o loop não é executado (o que é correto)
 for(i in seq_along(x)) print(i)

Vetorização, funções nativas e loops

Como vimos, o R é vetorizado. Muitas vezes, quando você pensar que precisa usar um loop, ao pensar melhor, descobrirá que não precisa. Em geral é possível resolver o problema de maneira vetorizada e usando funções nativas do R.

Para quem está aprendendo a programar diretamente com o R, isso é algo que virá naturalmente. Todavia, para quem já sabia programar em outras linguagens de programação – como C – pode ser difícil se acostumar a pensar desta maneira.

Vejamos um exemplo trivial. Suponha que você queira dividir os valores de um vetor x por 10. Se o R não fosse vetorizado, você teria que fazer algo como:

# criando vetor de exemplo
 x <- 10:20
 
 # divide cada elemento por 10
 for(i in seq_along(x)) 
   x[i] <- x[i]/10
 
 # resultado
 x
 ##  [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0

Mas o R é vetorizado e, portanto, este é o tipo de loop que não faz sentido na linguagem. É muito mais rápido e fácil de enteder escrever simplesmente x/10.

# recriando vetor de exemplo
 x <- 10:20
 
 # divide cada elemento por 10
 x <- x/10
 x
 ##  [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0

Vejamos um caso um pouco mais complicado. Suponha que você queira, gerar um passeio aleatório com um algoritmo simples: a cada período você pode andar para frente (+1) ou para trás (-1) com probabilidades iguais.

set.seed(1)
 
 # número de passos
 n <- 1000
 
 # vetor para armazenar o passeio aleatório
 passeio <- numeric(n)
 
 # primeiro passo
 passeio[1] <- sample(c(-1, 1), 1)
 
 # demais passos
 for(i in 2:n){
   
   # passo i é o onte você estava (passeio[i-1]) 
   # mais o passo seguinte
   passeio[i] <- passeio[i - 1] + sample(c(-1, 1), 1)
 }

É possível fazer tudo isso com apenas uma linha de maneira “vetorizada” e bem mais eficiente: crie todos os n passos de uma vez e faça a soma acumulada.

set.seed(1)
 passeio2 <- cumsum(sample(c(-1, 1), n, TRUE))
 
 # verifica se são iguais
 all.equal(passeio, passeio2)
 ## [1] TRUE

Então, você deve estar se perguntando: “não é para usar loops nunca”?

Não é isso. Em algumas situações loops são inevitáveis e podem inclusive ser mais fáceis de ler e de entender. O ponto aqui é apenas lembrá-lo de explorar a vetorização do R.

Voltando ao nosso exemplo do passeio aleatório, você deve ter notado a linha passeio <- numeric(n) em que criamos um vetor numérico para ir armazenando os resultados das iterações. Discutamos um pouco mais esse ponto.

Pré-alocar espaço antes do loop

Um erro bastante comum de quem está começando a programar em R é “crescer” objetos durante o loop. Isto tem um impacto substancial na performance do seu programa! Sempre que possível, crie um objeto, antes de iniciar o loop, para armazenar os resultados de cada iteração.

Vejamos um exemplo um pouco mais elaborado: vamos calcular os n primeiros números da sequência de Fibonacci: \(F_1 = 0, F_2 = 1, F_3 = 1, F_4 = 2, F_5 = 3, F_6 = 5, F_7 = 8, F_8 = 13, F_9 = 21 ...\)

Note que a sequência de Fibonacci pode ser definida da seguinte forma, os primeiros dois números são 0 e 1, isto é, \(F_1 = 0, F_2 = 1\). A partir daí, os números subsequentes são a soma dos dois números anteriores, isto é, \(F_i = F_{i-1} + F_{i-2}\) para todo \(i > 2\).

Vejamos uma forma de implementar isto no R usando for() e criando um vetor para armazenar os resultados:

n <- 9
 
 # crie um vetor de tamanho n 
 # para armazenar os n resultados
 fib <- numeric(n)
 
 # comece definindo as condições iniciais
 # F1 = 0 e F2 = 1
 fib[1] <- 0
 fib[2] <- 1
 
 # Agora para todo i > 2 
 # calculamos Fi = F(i-1) + F(i - 2)
 for(i in 3:n){
   fib[i] <- fib[i - 1] + fib[i - 2]
 }
 
 # conferindo resultado
 fib
 ## [1]  0  1  1  2  3  5  8 13 21

Vamos comparar a performance deste código com outro sem pré-alocar um vetor de resultados. Primeiro, transformemos nosso loop em uma função:

fib <- function(n){
   # vetor para armazenar resultados
   fib <- numeric(n)
   
   # condições iniciais
   fib[1] <- 0
   fib[2] <- 1
   
   # calculandos o números de 3 a n
   for(i in 3:n){
     fib[i] <- fib[i - 1] + fib[i - 2]
   }
   
   return(fib)
 }

Agora, criemos outra função em que o vetor fib cresce a cada iteração:

fib_sem_pre_alocar <- function(n){
   
   # condições iniciais
   fib    <- 0
   fib    <- c(fib, 1)
   
   # calculandos o números de 3 a n
   for(i in 3:n){
     fib <- c(fib, fib[i - 1] + fib[i - 2])
   }
   
   return(fib)
 }

Comparando as duas implementações:

library(microbenchmark)
 set.seed(5)
 microbenchmark(fib(5000), fib_sem_pre_alocar(5000))
 ## Unit: milliseconds
 ##                      expr  min   lq mean median   uq max neval
 ##                 fib(5000)  8.5  8.8  9.2    9.1  9.4  11   100
 ##  fib_sem_pre_alocar(5000) 58.4 60.7 67.5   62.2 64.4  99   100

Note que quanto maior o número de simulações, maior a queda na performance: com n = 5000 a função fib_sem_pre_alocar() chega a ser mais de 10 vezes mais lenta do que a função fib().

Exemplo: entendendo a família apply

Vamos calcular a média de cada uma das colunas do data.frame mtcars usando loops.

Para isso precisamos: (i) saber quantas colunas existem no data.frame; (ii) criar um vetor para armazenar os resultados; (iii) nomear o vetor de resultados com os nomes das colunas; e (iv) fazer um loop para cada coluna.

# (i) quantas colunas no data.frame
 n <- ncol(mtcars)
 
 # (ii) vetor para armazenar resultados
 medias <- numeric(n)
 
 # (iii) nomeando vetor com nomes das colunas
 names(medias) <- colnames(mtcars)
 
 # (iv) loop para cada coluna
 for(i in seq_along(mtcars)){
   medias[i] <- mean(mtcars[,i])
 }
 
 # resultado final
 medias
 ##    mpg    cyl   disp     hp   drat     wt   qsec     vs     am   gear   carb 
 ##  20.09   6.19 230.72 146.69   3.60   3.22  17.85   0.44   0.41   3.69   2.81

Gastamos várias linhas para fazer essa simples operação. Como já vimos, é bastante fácil fazer isso no R com apenas uma linha:

sapply(mtcars, mean)
 ##    mpg    cyl   disp     hp   drat     wt   qsec     vs     am   gear   carb 
 ##  20.09   6.19 230.72 146.69   3.60   3.22  17.85   0.44   0.41   3.69   2.81

Imagine que não existisse a função sapply() no R. Se quiséssemos aplicar outra função para cada coluna, teríamos que copiar e colar todo o código novamente, certo?

Sim, você poderia fazer isso, mas não seria uma boa prática. Neste caso, como já vimos, o ideal seria criar uma função.

Façamos, portanto, uma função que nos permita aplicar uma fução arbitrária nas colunas de um data.frame.

meu_sapply <- function(x, funcao){
   
   n <- length(x)
   
   resultado <- numeric(n)
   
   names(resultado) <- names(x)
   
   for(i in seq_along(x)){
     resultado[i] <- funcao(x[[i]])
   }
   
   return(resultado)
 }

Perceba que ficou bastante simples percorrer todas as colunas de um data.frame para aplicar a função que você quiser:

meu_sapply(mtcars, mean)
 ##    mpg    cyl   disp     hp   drat     wt   qsec     vs     am   gear   carb 
 ##  20.09   6.19 230.72 146.69   3.60   3.22  17.85   0.44   0.41   3.69   2.81
 
 meu_sapply(mtcars, sd)
 ##    mpg    cyl   disp     hp   drat     wt   qsec     vs     am   gear   carb 
 ##   6.03   1.79 123.94  68.56   0.53   0.98   1.79   0.50   0.50   0.74   1.62
 
 meu_sapply(mtcars, max)
 ##   mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb 
 ##  33.9   8.0 472.0 335.0   4.9   5.4  22.9   1.0   1.0   5.0   8.0
 
 meu_sapply(mtcars, min)
 ##  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
 ## 10.4  4.0 71.1 52.0  2.8  1.5 14.5  0.0  0.0  3.0  1.0

É isso o que as funções da família apply são: são funções que fazem loops para você. Elas automaticamente cuidam de toda a parte chata do loop como, por exemplo, criar um objeto de tamanho correto para pré-alocar os resultados. Além disso, em grande parte das vezes essas funções serão mais eficientes do que se você mesmo fizer a implementação.

Por curiosidade, vamos comparar a eficiência do sapply() do R com meu_sapply()

microbenchmark(sapply(mtcars, mean), meu_sapply(mtcars, mean))
 ## Unit: microseconds
 ##                      expr min  lq mean median  uq max neval
 ##      sapply(mtcars, mean)  68  71   88     74  87 358   100
 ##  meu_sapply(mtcars, mean) 131 135  157    147 158 391   100

Exercícios

As funções que você irá implementar aqui, usando for(), serão até mais de 100 vezes mais lentas do que as funções nativas do R. Estes exercícios são para você treinar a construção de loops, um pouco de lógica de programação, e entender o que as funções do R estão fazendo – de maneira geral – por debaixo dos panos.

  1. Crie uma função que encontre o máximo de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função max() do R. Sua função é quantas vezes mais lenta?

  2. Crie uma função que calcule o fatorial de n (use for() na sua função). Compare os resultados e a performance de sua implementação com a função factorial() do R. Sua função é quantas vezes mais lenta?

  3. Crie uma função que calcule a soma de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função sum() do R. Sua função é quantas vezes mais lenta?

  4. Crie uma função que calcule a soma acumulada de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função cumsum() do R. Sua função é quantas vezes mais lenta?

Respostas

Criando vetor aleatório para comparar as funções:

# cria vetor para comparar resultados
 set.seed(123)
 x <- rnorm(100)
 
 # Pacote para comparar resultados 
 library(microbenchmark)

Resposta sugerida ex-1:

# 1) loop para encontrar máximo
 
 max_loop <- function(x){
   max <- x[1]
   
   for(i in 2:length(x)){
     if(x[i] > max){
       max <- x[i]
     }
     
   }
   return(max)
 }
 
 all.equal(max(x), max_loop(x))
 ## [1] TRUE
 
 microbenchmark(max(x), max_loop(x))
 ## Unit: nanoseconds
 ##         expr   min    lq  mean median    uq    max neval
 ##       max(x)     0     1   243      1   467   2799   100
 ##  max_loop(x) 62049 70447 72556  72780 73713 130163   100

Resposta sugerida ex-2:

# 2) loop para fatorial
 
 fatorial <- function(n){
   fat <- 1
   for(i in 1:n){
     fat <- fat*i
   }
   return(fat)
 }
 
 all.equal(factorial(10), fatorial(10))
 ## [1] TRUE
 
 microbenchmark(factorial(10), fatorial(10))
 ## Warning in microbenchmark(factorial(10), fatorial(10)): Could not measure a positive execution time
 ## for 2 evaluations.
 ## Unit: nanoseconds
 ##           expr  min   lq mean median   uq   max neval
 ##  factorial(10)    0    0  182      1  467  2333   100
 ##   fatorial(10) 5132 5599 8296   6066 7932 39189   100

Resposta sugerida ex-3:

# 3) loop para soma 
 
 soma <- function(x){
   
   n <- length(x)
 
   soma <- numeric(n)
   
   soma <- x[1]
   
   for(i in 2:n){
     soma <- x[i] + soma
   }
   
   return(soma)
 }
 
 all.equal(soma(x), sum(x))
 ## [1] TRUE
 
 microbenchmark(sum(x), soma(x))
 ## Warning in microbenchmark(sum(x), soma(x)): Could not measure a positive execution time for 6
 ## evaluations.
 ## Unit: nanoseconds
 ##     expr   min    lq  mean median    uq    max neval
 ##   sum(x)     0     0   206      1   467   1866   100
 ##  soma(x) 57384 66715 75625  68114 74412 133896   100

Resposta sugerida ex-4:

# 4) loop para soma acumulada
 
 soma_acumulada <- function(x){
   
   n <- length(x)
 
   soma <- numeric(n)
   
   soma[1] <- x[1]
   
   for(i in 2:n){
     soma[i] <- x[i] + soma[i-1]
   }
   
   return(soma)
 }
 
 all.equal(soma_acumulada(x), cumsum(x))
 ## [1] TRUE
 
 microbenchmark(cumsum(x), soma_acumulada(x))
 ## Unit: nanoseconds
 ##               expr    min     lq   mean median     uq    max neval
 ##          cumsum(x)      0    467    714    467    934   4666   100
 ##  soma_acumulada(x) 166552 178683 215683 187313 252861 471665   100

Nota

Este texto foi originalmente publicado no blog Análise Real.

Loops: for()

Um loop utilizando for() no R tem a seguinte estrutura básica:

for(i in conjunto_de_valores){
   # comandos que 
   # serão repetidos
 }
  • O início do loop se dá com o comando for seguido de parênteses e chaves;
  • Dentro do parênteses temos um indicador que será usado durante o loop (no caso escolhemos o nome i) e um conjunto de valores que será iterado (conjunto_de_valores).
  • Dentro das chaves temos o bloco de código que será executado durante o loop.

Em outras palavras, no comando acima estamos dizendo que para cada elemento i contido no conjunto_de_valores iremos executar os comandos que estão dentro das chaves.

Para facilitar o entendimento, vejamos dois exemplos muito simples. Primeiro, vamos imprimir na tela os números de 1 a 5.

for(i in 1:5){
   print(i)
 }
 ## [1] 1
 ## [1] 2
 ## [1] 3
 ## [1] 4
 ## [1] 5

Agora, vamos imprimir na tela as 5 primeiras letras do alfabeto (o R já vem com um vetor com as letras do alfabeto: letters).

for(i in 1:5){
   print(letters[i])
 }
 ## [1] "a"
 ## [1] "b"
 ## [1] "c"
 ## [1] "d"
 ## [1] "e"

No mesmo exemplo, acima, ao invés correr o loop no índice de inteiros 1:5, vamos iterar diretamente sobre os primeiros 5 elementos do vetor letters:


 for(letra in letters[1:5]){
   print(letra)
 }
 ## [1] "a"
 ## [1] "b"
 ## [1] "c"
 ## [1] "d"
 ## [1] "e"

seq_along

Uma função bastante útil ao fazer loops é a função seg_along(). Ela cria um vetor de inteiros com índices para acompanhar o objeto.

# criando um vetor de exemplo
 set.seed(119)
 x <- rnorm(10)
 
 # inteiros de 1 a 10
 seq_along(x)
 ##  [1]  1  2  3  4  5  6  7  8  9 10

Também é possível criar um vetor de inteiros do tamanho do objeto fazendo uma sequência de 1 até length(x):

1:length(x)
 ##  [1]  1  2  3  4  5  6  7  8  9 10

Entretanto, a vantagem de seq_along() é que quando o vetor é vazio, ela retorna um vetor vazio e, deste modo, o loop não é executado (o que é o comportamento correto).

Já a sequência 1:length(x) retorna a sequência 1:0, isto é, uma sequência decrescente de 1 até 0, e loop é executado nestes valores.

Vejamos:

# cria vetor vazio
 x <- numeric(0)
 
 # 1:length(x)
 # note que o loop é executado (o que é errado)
 for(i in 1:length(x)) print(i)
 ## [1] 1
 ## [1] 0
 
 # seq_along
 # note que o loop não é executado (o que é correto)
 for(i in seq_along(x)) print(i)

Vetorização, funções nativas e loops

Como vimos, o R é vetorizado. Muitas vezes, quando você pensar que precisa usar um loop, ao pensar melhor, descobrirá que não precisa. Em geral é possível resolver o problema de maneira vetorizada e usando funções nativas do R.

Para quem está aprendendo a programar diretamente com o R, isso é algo que virá naturalmente. Todavia, para quem já sabia programar em outras linguagens de programação – como C – pode ser difícil se acostumar a pensar desta maneira.

Vejamos um exemplo trivial. Suponha que você queira dividir os valores de um vetor x por 10. Se o R não fosse vetorizado, você teria que fazer algo como:

# criando vetor de exemplo
 x <- 10:20
 
 # divide cada elemento por 10
 for(i in seq_along(x)) 
   x[i] <- x[i]/10
 
 # resultado
 x
 ##  [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0

Mas o R é vetorizado e, portanto, este é o tipo de loop que não faz sentido na linguagem. É muito mais rápido e fácil de enteder escrever simplesmente x/10.

# recriando vetor de exemplo
 x <- 10:20
 
 # divide cada elemento por 10
 x <- x/10
 x
 ##  [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0

Vejamos um caso um pouco mais complicado. Suponha que você queira, gerar um passeio aleatório com um algoritmo simples: a cada período você pode andar para frente (+1) ou para trás (-1) com probabilidades iguais.

set.seed(1)
 
 # número de passos
 n <- 1000
 
 # vetor para armazenar o passeio aleatório
 passeio <- numeric(n)
 
 # primeiro passo
 passeio[1] <- sample(c(-1, 1), 1)
 
 # demais passos
 for(i in 2:n){
   
   # passo i é o onte você estava (passeio[i-1]) 
   # mais o passo seguinte
   passeio[i] <- passeio[i - 1] + sample(c(-1, 1), 1)
 }

É possível fazer tudo isso com apenas uma linha de maneira “vetorizada” e bem mais eficiente: crie todos os n passos de uma vez e faça a soma acumulada.

set.seed(1)
 passeio2 <- cumsum(sample(c(-1, 1), n, TRUE))
 
 # verifica se são iguais
 all.equal(passeio, passeio2)
 ## [1] TRUE

Então, você deve estar se perguntando: “não é para usar loops nunca”?

Não é isso. Em algumas situações loops são inevitáveis e podem inclusive ser mais fáceis de ler e de entender. O ponto aqui é apenas lembrá-lo de explorar a vetorização do R.

Voltando ao nosso exemplo do passeio aleatório, você deve ter notado a linha passeio <- numeric(n) em que criamos um vetor numérico para ir armazenando os resultados das iterações. Discutamos um pouco mais esse ponto.

Pré-alocar espaço antes do loop

Um erro bastante comum de quem está começando a programar em R é “crescer” objetos durante o loop. Isto tem um impacto substancial na performance do seu programa! Sempre que possível, crie um objeto, antes de iniciar o loop, para armazenar os resultados de cada iteração.

Vejamos um exemplo um pouco mais elaborado: vamos calcular os n primeiros números da sequência de Fibonacci: \(F_1 = 0, F_2 = 1, F_3 = 1, F_4 = 2, F_5 = 3, F_6 = 5, F_7 = 8, F_8 = 13, F_9 = 21 ...\)

Note que a sequência de Fibonacci pode ser definida da seguinte forma, os primeiros dois números são 0 e 1, isto é, \(F_1 = 0, F_2 = 1\). A partir daí, os números subsequentes são a soma dos dois números anteriores, isto é, \(F_i = F_{i-1} + F_{i-2}\) para todo \(i > 2\).

Vejamos uma forma de implementar isto no R usando for() e criando um vetor para armazenar os resultados:

n <- 9
 
 # crie um vetor de tamanho n 
 # para armazenar os n resultados
 fib <- numeric(n)
 
 # comece definindo as condições iniciais
 # F1 = 0 e F2 = 1
 fib[1] <- 0
 fib[2] <- 1
 
 # Agora para todo i > 2 
 # calculamos Fi = F(i-1) + F(i - 2)
 for(i in 3:n){
   fib[i] <- fib[i - 1] + fib[i - 2]
 }
 
 # conferindo resultado
 fib
 ## [1]  0  1  1  2  3  5  8 13 21

Vamos comparar a performance deste código com outro sem pré-alocar um vetor de resultados. Primeiro, transformemos nosso loop em uma função:

fib <- function(n){
   # vetor para armazenar resultados
   fib <- numeric(n)
   
   # condições iniciais
   fib[1] <- 0
   fib[2] <- 1
   
   # calculandos o números de 3 a n
   for(i in 3:n){
     fib[i] <- fib[i - 1] + fib[i - 2]
   }
   
   return(fib)
 }

Agora, criemos outra função em que o vetor fib cresce a cada iteração:

fib_sem_pre_alocar <- function(n){
   
   # condições iniciais
   fib    <- 0
   fib    <- c(fib, 1)
   
   # calculandos o números de 3 a n
   for(i in 3:n){
     fib <- c(fib, fib[i - 1] + fib[i - 2])
   }
   
   return(fib)
 }

Comparando as duas implementações:

library(microbenchmark)
 set.seed(5)
 microbenchmark(fib(5000), fib_sem_pre_alocar(5000))
 ## Unit: milliseconds
 ##                      expr  min   lq mean median   uq max neval
 ##                 fib(5000)  8.5  8.8  9.2    9.1  9.4  11   100
 ##  fib_sem_pre_alocar(5000) 58.4 60.7 67.5   62.2 64.4  99   100

Note que quanto maior o número de simulações, maior a queda na performance: com n = 5000 a função fib_sem_pre_alocar() chega a ser mais de 10 vezes mais lenta do que a função fib().

Exemplo: entendendo a família apply

Vamos calcular a média de cada uma das colunas do data.frame mtcars usando loops.

Para isso precisamos: (i) saber quantas colunas existem no data.frame; (ii) criar um vetor para armazenar os resultados; (iii) nomear o vetor de resultados com os nomes das colunas; e (iv) fazer um loop para cada coluna.

# (i) quantas colunas no data.frame
 n <- ncol(mtcars)
 
 # (ii) vetor para armazenar resultados
 medias <- numeric(n)
 
 # (iii) nomeando vetor com nomes das colunas
 names(medias) <- colnames(mtcars)
 
 # (iv) loop para cada coluna
 for(i in seq_along(mtcars)){
   medias[i] <- mean(mtcars[,i])
 }
 
 # resultado final
 medias
 ##    mpg    cyl   disp     hp   drat     wt   qsec     vs     am   gear   carb 
 ##  20.09   6.19 230.72 146.69   3.60   3.22  17.85   0.44   0.41   3.69   2.81

Gastamos várias linhas para fazer essa simples operação. Como já vimos, é bastante fácil fazer isso no R com apenas uma linha:

sapply(mtcars, mean)
 ##    mpg    cyl   disp     hp   drat     wt   qsec     vs     am   gear   carb 
 ##  20.09   6.19 230.72 146.69   3.60   3.22  17.85   0.44   0.41   3.69   2.81

Imagine que não existisse a função sapply() no R. Se quiséssemos aplicar outra função para cada coluna, teríamos que copiar e colar todo o código novamente, certo?

Sim, você poderia fazer isso, mas não seria uma boa prática. Neste caso, como já vimos, o ideal seria criar uma função.

Façamos, portanto, uma função que nos permita aplicar uma fução arbitrária nas colunas de um data.frame.

meu_sapply <- function(x, funcao){
   
   n <- length(x)
   
   resultado <- numeric(n)
   
   names(resultado) <- names(x)
   
   for(i in seq_along(x)){
     resultado[i] <- funcao(x[[i]])
   }
   
   return(resultado)
 }

Perceba que ficou bastante simples percorrer todas as colunas de um data.frame para aplicar a função que você quiser:

meu_sapply(mtcars, mean)
 ##    mpg    cyl   disp     hp   drat     wt   qsec     vs     am   gear   carb 
 ##  20.09   6.19 230.72 146.69   3.60   3.22  17.85   0.44   0.41   3.69   2.81
 
 meu_sapply(mtcars, sd)
 ##    mpg    cyl   disp     hp   drat     wt   qsec     vs     am   gear   carb 
 ##   6.03   1.79 123.94  68.56   0.53   0.98   1.79   0.50   0.50   0.74   1.62
 
 meu_sapply(mtcars, max)
 ##   mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb 
 ##  33.9   8.0 472.0 335.0   4.9   5.4  22.9   1.0   1.0   5.0   8.0
 
 meu_sapply(mtcars, min)
 ##  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
 ## 10.4  4.0 71.1 52.0  2.8  1.5 14.5  0.0  0.0  3.0  1.0

É isso o que as funções da família apply são: são funções que fazem loops para você. Elas automaticamente cuidam de toda a parte chata do loop como, por exemplo, criar um objeto de tamanho correto para pré-alocar os resultados. Além disso, em grande parte das vezes essas funções serão mais eficientes do que se você mesmo fizer a implementação.

Por curiosidade, vamos comparar a eficiência do sapply() do R com meu_sapply()

microbenchmark(sapply(mtcars, mean), meu_sapply(mtcars, mean))
 ## Unit: microseconds
 ##                      expr min  lq mean median  uq max neval
 ##      sapply(mtcars, mean)  68  71   88     74  87 358   100
 ##  meu_sapply(mtcars, mean) 131 135  157    147 158 391   100

Exercícios

As funções que você irá implementar aqui, usando for(), serão até mais de 100 vezes mais lentas do que as funções nativas do R. Estes exercícios são para você treinar a construção de loops, um pouco de lógica de programação, e entender o que as funções do R estão fazendo – de maneira geral – por debaixo dos panos.

  1. Crie uma função que encontre o máximo de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função max() do R. Sua função é quantas vezes mais lenta?

  2. Crie uma função que calcule o fatorial de n (use for() na sua função). Compare os resultados e a performance de sua implementação com a função factorial() do R. Sua função é quantas vezes mais lenta?

  3. Crie uma função que calcule a soma de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função sum() do R. Sua função é quantas vezes mais lenta?

  4. Crie uma função que calcule a soma acumulada de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função cumsum() do R. Sua função é quantas vezes mais lenta?

Respostas

Criando vetor aleatório para comparar as funções:

# cria vetor para comparar resultados
 set.seed(123)
 x <- rnorm(100)
 
 # Pacote para comparar resultados 
 library(microbenchmark)

Resposta sugerida ex-1:

# 1) loop para encontrar máximo
 
 max_loop <- function(x){
   max <- x[1]
   
   for(i in 2:length(x)){
     if(x[i] > max){
       max <- x[i]
     }
     
   }
   return(max)
 }
 
 all.equal(max(x), max_loop(x))
 ## [1] TRUE
 
 microbenchmark(max(x), max_loop(x))
 ## Unit: nanoseconds
 ##         expr   min    lq  mean median    uq    max neval
 ##       max(x)     0     1   243      1   467   2799   100
 ##  max_loop(x) 62049 70447 72556  72780 73713 130163   100

Resposta sugerida ex-2:

# 2) loop para fatorial
 
 fatorial <- function(n){
   fat <- 1
   for(i in 1:n){
     fat <- fat*i
   }
   return(fat)
 }
 
 all.equal(factorial(10), fatorial(10))
 ## [1] TRUE
 
 microbenchmark(factorial(10), fatorial(10))
 ## Warning in microbenchmark(factorial(10), fatorial(10)): Could not measure a positive execution time
 ## for 2 evaluations.
 ## Unit: nanoseconds
 ##           expr  min   lq mean median   uq   max neval
 ##  factorial(10)    0    0  182      1  467  2333   100
 ##   fatorial(10) 5132 5599 8296   6066 7932 39189   100

Resposta sugerida ex-3:

# 3) loop para soma 
 
 soma <- function(x){
   
   n <- length(x)
 
   soma <- numeric(n)
   
   soma <- x[1]
   
   for(i in 2:n){
     soma <- x[i] + soma
   }
   
   return(soma)
 }
 
 all.equal(soma(x), sum(x))
 ## [1] TRUE
 
 microbenchmark(sum(x), soma(x))
 ## Warning in microbenchmark(sum(x), soma(x)): Could not measure a positive execution time for 6
 ## evaluations.
 ## Unit: nanoseconds
 ##     expr   min    lq  mean median    uq    max neval
 ##   sum(x)     0     0   206      1   467   1866   100
 ##  soma(x) 57384 66715 75625  68114 74412 133896   100

Resposta sugerida ex-4:

# 4) loop para soma acumulada
 
 soma_acumulada <- function(x){
   
   n <- length(x)
 
   soma <- numeric(n)
   
   soma[1] <- x[1]
   
   for(i in 2:n){
     soma[i] <- x[i] + soma[i-1]
   }
   
   return(soma)
 }
 
 all.equal(soma_acumulada(x), cumsum(x))
 ## [1] TRUE
 
 microbenchmark(cumsum(x), soma_acumulada(x))
 ## Unit: nanoseconds
 ##               expr    min     lq   mean median     uq    max neval
 ##          cumsum(x)      0    467    714    467    934   4666   100
 ##  soma_acumulada(x) 166552 178683 215683 187313 252861 471665   100
ggplot2

ggplot2

Remuneração por sexo e cor

Remuneração por sexo e cor

ggplot2

Concepção do ggplot2

O ggplot2 é mais do que um pacote para fazer gráficos; ele é uma tentativa (muito bem sucedida) trazer para o dia-a-dia dos técnicos uma gramática dos gráficos.

Por que uma gramática dos gráficos?

Através dela podemos definir sistematicamente quais são os componentes de um gráficos e como eles se interelacionam.

Veja mais informações em http://docs.ggplot2.org/.

A gramática dos gráficos

elemento exemplos
dados (informação)* seguro, fiscalizações
(a)estética* cor, formato
geometrias* barra, ponto
estatísticas mediana, máximo
facetas facetas
coordenadas polar, cartesiana
t(h)emas eixos, título

A camada de dados

A primeira etapa da construção de um gráfico é ter os dados que serão representados graficamente. Vamos carregar os dados dos vínculos da RAIS em 2014 para Santa Catarina

vinculos_2014 <- readRDS('Microdados/vinculos_SC_2014.RDS')
 vinculos_2014 <- as.data.frame(vinculos_2014) 
                  # Para tirar a classe data.table,
                  # não apresentada neste curso
 
 vinculos_2014 <- vinculos_2014[, c("Idade", "Mês Admissão",
                          "Raça Cor" , "Sexo Trabalhador",
                          "Vl Remun Média Nom", "Qtd Hora Contr")]

Ainda os dados

Ainda precisamos melhorar um pouco a forma como os dados chegaram. Idade poderia ser mais útil em faixas; Mês Admissão, Cor e Sexo são variáveis categóricas.

library(dplyr)
 vinculos_2014 <- vinculos_2014 %>%
   mutate(Admissão = factor(`Mês Admissão`,
                            labels = c('Não admitido ano', 'Jan', 'Fev',
                                       'Mar', 'Abr', 'Mai', 'Jun', 'Jul',
                                       'Ago', 'Set', 'Out', 'Nov', 'Dez')),
          Cor = factor(`Raça Cor`,
                       labels = c("Indígena", "Branca", "Preta", "Amarela",
                                  "Parda", "Não identificado", "Ignorado")),
          Sexo = factor(`Sexo Trabalhador`,
                        labels = c("Masculino", "Feminino")),
          Remuneração = as.numeric(gsub(pattern = ",", ".",
                                        `Vl Remun Média Nom`)))

Faixas de Jornadas

vinculos_2014$Jornada <- cut(vinculos_2014$`Qtd Hora Contr`,
                              breaks = c(-Inf, 20, 30, 40, Inf),
                              labels = c("Até 20", "Entre 20 e 30",
                                         "Entre 30 e 40", "Mais de 40"))
 
 set.seed(1) # semente de pseudo-aleatoriedade para reprodução
 
 # Vamos reduzir os dados para 200 observações aleatórias
 dados <- vinculos_2014[sample(1:nrow(vinculos_2014), 200), ]

Aspectos Estéticos

Aqui apenas falar da diferença entre dado e estética.

Mapeando dados na estética

Imagine que você fosse desenhar um gráfico. Como você decidiria até onde deve ir a barra ou onde ficariam os pontos? O computador também precisa de critérios para decidir como representar os dados, como a idade dos trabalhadores de Santa Catarina, em um gráfico.

Assim, a idade dos trabalhadores pode ser representada no eixo horizontal ou os grupos de idade podem aparecer como cores ou formato dos dados (menores de 30 triângulos, maiores quadrados).

Talvez copiar um slide do DataCamp seja meelhor

Explicar o que é mapear dados em elementos estéticos do gráfico

Atributos Estéticos

É diferente mapear uma estética e definir um atributo estético. Mapear um atributo estético é dizer que a cor representa
Falar da diferença de atribuir uma cor ou tamanho ao gráfico e mapeá-los.

Aspectos Geométricos

Além de ter dados e mapeá-los em atributos estéticos, você deve escolher com que geometrias quer aprensentar seus dados.

As geometrias mais comuns são:

  • Pontos (diagrama de dispersão)

  • Barras

  • Linhas

  • Diagrama de baixa (boxplot)

Vamos ver como usar estas geometrias no ggplot2

geom_point()

ggplot(dados[dados$Remuneração < 10000, ], aes(Idade, Remuneração)) +
   geom_point(aes(color = Jornada), size = 2, alpha = 0.6)

geom_bar()

medias <- vinculos_2014 %>% group_by(Jornada, Sexo) %>%
   summarise(Media = mean(Remuneração))
 
 ggplot(medias, aes(x = Jornada, y = Media, fill = Sexo)) +
   geom_bar(stat = "identity")

# gráficos de barras são geralmente usados em estatísticas sumarizadas

geom_histogram()

ggplot(vinculos_2014 %>% filter(Remuneração < 5000),
        aes(x = Remuneração, fill = Sexo)) +
   geom_histogram(binwidth = 500, alpha= 0.8) # definir qtd de intervalos(bins)

Ou então geom_density()

ggplot(vinculos_2014 %>% filter(Remuneração < 5000),
        aes(x = Remuneração, fill = Sexo)) +
   geom_density(alpha= 0.6)

geom_line()

medias <- dados %>% group_by(Jornada) %>%
   summarise(Media = mean(Remuneração))
 
 ggplot(dados[dados$Remuneração < 10000,], aes(Idade, Remuneração, col = Jornada)) +
   geom_point(alpha = 0.6) +
   geom_hline(aes(yintercept = Media, col = Jornada), data = medias, size = 1.5)

geom_boxplot()

ggplot(vinculos_2014 %>% filter(Remuneração < 10000),
        aes(x = Sexo, y = Remuneração, fill = Sexo)) +
   geom_boxplot()

Estatísticas

Toda geometria está vinculada a alguma estatística. Esta estatística pode ser “identidade”, isto é, o dados tal qual. Mas, observem, seria impossível desenhar algumas geometrias sem obter algumas estatísticas sobre os dados. O ggplot2 faz isso por trás das cenas para nós.

Por exemplo, para desenhar um diagrama de caida sobre precisa saber: i) o desvio padrão, ii), a mediana, iii) os 1º e 3º quartis. Um histograma, por sua vez, precisa i) definir classes de intervalos e ii) contar a quantidade de dados nestes intervalos.

Caso precisemos gerar as estatísticas, o ggplot2 tem a família de funções stat_*. Vejamos um exemplo

Estatísticas

ggplot(dados, aes(x = Idade, y = Remuneração, col = Jornada)) +
   geom_point() +
   stat_smooth(geom = "smooth", method = "lm", level = 0.8)

# realizou calculos de regressão

Coordenadas e Facetas

menos_5 <- vinculos_2014[(vinculos_2014$Cor %in%
                               c("Preta", "Branca") &
                        vinculos_2014$Remuneração < 5000), ]
 ggplot(menos_5, aes(Remuneração, fill = Sexo)) +
   geom_density(alpha = 0.5) + facet_wrap(~Cor, dir = "v")

Tema

ggplot(menos_5, aes(Remuneração, fill = Sexo)) +
   geom_density(alpha = 0.5) +   facet_wrap(~Cor, dir = "v") +
   theme_classic() +
   scale_y_continuous(name = "Densidade", labels = c("0", "0,05%", "0,1%", "0,15%"), minor_breaks = NULL) +
     scale_x_continuous(labels = c("R$ 0", "R$ 1.000", "R$ 2.000", "R$ 3.000", "R$ 4.000", "R$ 5.000"), minor_breaks = NULL) + 
   theme(legend.position = 'none')